/**
 * 
 */
package za.co.sindi.oauth.core.scanner.impl;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;

import za.co.sindi.oauth.core.scanner.AbstractClassScanner;
import za.co.sindi.oauth.exception.ScanningException;

/**
 * @author Buhake Sindi
 * @since 28 March 2012
 *
 */
public class WebArchiveClassScanner extends AbstractClassScanner {

	private static final String WEBINF_FOLDER = "/WEB-INF";
	private static final String WEBINF_LIB_FOLDER = WEBINF_FOLDER + "/lib";
	private static final String WEBINF_CLASSES_FOLDER = WEBINF_FOLDER + "/classes";
	private final ServletContext servletContext;

	/**
	 * @param basePath
	 * @param servletContext
	 */
	public WebArchiveClassScanner(ServletContext servletContext) {
		if (servletContext == null) {
			throw new IllegalArgumentException("ServletContext may not be null.");
		}
		
		this.servletContext = servletContext;
	}
	
	/**
	 * 
	 * @param sce
	 */
	public WebArchiveClassScanner(ServletContextEvent sce) {
		this(sce.getServletContext());
	}

	/* (non-Javadoc)
	 * @see com.neurologic.scanner.ClassScanner#scan(java.lang.String)
	 */
	@Override
	public Set<Class<?>> scan(String basePackageName) throws ScanningException {
		// TODO Auto-generated method stub
		if (basePackageName == null) {
			throw new IllegalArgumentException("Base package name may not be null.");
		}
		
		Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
		
		try {
			//Scan WEB-INF/classes folder.
			if (logger.isInfoEnabled()) {
				logger.info("Scanning " + WEBINF_CLASSES_FOLDER + " folder.");
			}
			scan(WEBINF_CLASSES_FOLDER + "/" + basePackageName.replace('.', '/'), basePackageName, classes);
			
			//Scan WEB-INF/lib folder.
			if (logger.isInfoEnabled()) {
				logger.info("Scanning " + WEBINF_LIB_FOLDER + " folder.");
			}
			scan(WEBINF_LIB_FOLDER, basePackageName, classes);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			throw new ScanningException(e.getLocalizedMessage(), e);
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			throw new ScanningException(e.getLocalizedMessage(), e);
		}
		
		return Collections.unmodifiableSet(classes);
	}
	
	private void scan(String basePath, String basePackageName, Set<Class<?>> classes) throws IOException, ClassNotFoundException {
		Set<String> dirs = servletContext.getResourcePaths(basePath);
		if (dirs != null) {
			for (String path : dirs) {
				if (path.endsWith("/")) {
					scan(path, basePackageName, classes);
				} else {
					processFile(path, basePackageName.replace('.', '/'), classes);
				}
			}
		}
	}
	
	private void processFile(String path, String packagePath, Set<Class<?>> classes) throws ClassNotFoundException, IOException {		
		String file = null;
		
		if (path.endsWith(JAVA_CLASS_EXTENSION)) {
			if (path.contains(packagePath)) {
				file = path.substring(path.indexOf(packagePath));
				String className = file.substring(0, file.indexOf(JAVA_CLASS_EXTENSION)).replace('/', '.');
				Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
				if (match(clazz)) {
					if (logger.isInfoEnabled()) {
						logger.info("Found matched class '" + clazz.getName() + "' in path '" + path + "'. Adding to set.");
					}
					
					classes.add(clazz);
				}
			}
		} else if (path.endsWith(JAVA_JAR_EXTENSION)) {
			String realPath = servletContext.getRealPath(path);
			if (realPath == null) {
				file = servletContext.getResource(path).toString();
			} else {
				file = new File(realPath).toURI().toURL().toString();
			}
			
			readJarEntries(new URL("jar", "", -1, file + "!/"), packagePath, classes);
		}
	}
}
